$mac!(rustc);
$mac!(search);
$mac!(test);
+ $mac!(uninstall);
$mac!(update);
$mac!(verify_project);
$mac!(version);
-use cargo::ops;
-use cargo::util::{CliResult, CliError, Config};
use std::path::Path;
-#[allow(dead_code)] // for now until all options are implemented
+use cargo::ops;
+use cargo::core::{SourceId, GitReference};
+use cargo::util::{CliResult, Config, ToUrl, human};
#[derive(RustcDecodable)]
struct Options {
flag_features: Vec<String>,
flag_no_default_features: bool,
flag_debug: bool,
- flag_bin: Option<String>,
+ flag_bin: Vec<String>,
flag_example: Vec<String>,
- flag_package: Vec<String>,
flag_verbose: bool,
+ flag_quiet: bool,
+ flag_color: Option<String>,
flag_root: Option<String>,
+ flag_list: bool,
+
+ arg_crate: Option<String>,
+ flag_vers: Option<String>,
+
+ flag_git: Option<String>,
+ flag_branch: Option<String>,
+ flag_tag: Option<String>,
+ flag_rev: Option<String>,
+
+ flag_path: Option<String>,
}
pub const USAGE: &'static str = "
-Install a crate onto the local system
+Install a Rust binary
-Installing new crates:
- cargo install [options]
- cargo install [options] [-p CRATE | --package CRATE] [--vers VERS]
- cargo install [options] --git URL [--branch BRANCH | --tag TAG | --rev SHA]
- cargo install [options] --path PATH
-
-Managing installed crates:
+Usage:
+ cargo install [options] [<crate>]
cargo install [options] --list
-Options:
- -h, --help Print this message
- -j N, --jobs N The number of jobs to run in parallel
- --features FEATURES Space-separated list of features to activate
- --no-default-features Do not build the `default` feature
- --debug Build in debug mode instead of release mode
- --bin NAME Only install the binary NAME
- --example EXAMPLE Install the example EXAMPLE instead of binaries
- -p, --package CRATE Install this crate from crates.io or select the
- package in a repository/path to install.
- -v, --verbose Use verbose output
- --root DIR Directory to install packages into
+Specifying what crate to install:
+ --vers VERS Specify a version to install from crates.io
+ --git URL Git URL to install the specified crate from
+ --branch BRANCH Branch to use when installing from git
+ --tag TAG Tag to use when installing from git
+ --rev SHA Specific commit to use when installing from git
+ --path PATH Filesystem path to local crate to install
+
+Build and install options:
+ -h, --help Print this message
+ -j N, --jobs N The number of jobs to run in parallel
+ --features FEATURES Space-separated list of features to activate
+ --no-default-features Do not build the `default` feature
+ --debug Build in debug mode instead of release mode
+ --bin NAME Only install the binary NAME
+ --example EXAMPLE Install the example EXAMPLE instead of binaries
+ --root DIR Directory to install packages into
+ -v, --verbose Use verbose output
+ -q, --quiet Less output printed to stdout
+ --color WHEN Coloring: auto, always, never
This command manages Cargo's local set of install binary crates. Only packages
which have [[bin]] targets can be installed, and all binaries are installed into
-`$HOME/.cargo/bin` by default (or `$CARGO_HOME/bin` if you change the home
-directory).
+the installation root's `bin` folder. The installation root is determined, in
+order of precedence, by `--root`, `$CARGO_INSTALL_ROOT`, the `install.root`
+configuration key, and finally the home directory (which is either
+`$CARGO_HOME` if set or `$HOME/.cargo` by default).
-There are multiple methods of installing a new crate onto the system. The
-`cargo install` command with no arguments will install the current crate (as
-specifed by the current directory). Otherwise the `-p`, `--package`, `--git`,
-and `--path` options all specify the source from which a crate is being
-installed. The `-p` and `--package` options will download crates from crates.io.
+There are multiple sources from which a crate can be installed. The default
+location is crates.io but the `--git` and `--path` flags can change this source.
+If the source contains more than one package (such as crates.io or a git
+repository with multiple crates) the `<crate>` argument is required to indicate
+which crate should be installed.
Crates from crates.io can optionally specify the version they wish to install
via the `--vers` flags, and similarly packages from git repositories can
";
pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
- config.shell().set_verbose(options.flag_verbose);
+ try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet));
+ try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..])));
let compile_opts = ops::CompileOptions {
config: config,
target: None,
features: &options.flag_features,
no_default_features: options.flag_no_default_features,
- spec: None,
+ spec: &[],
exec_engine: None,
mode: ops::CompileMode::Build,
- release: true,
- filter: ops::CompileFilter::Everything,
+ release: !options.flag_debug,
+ filter: ops::CompileFilter::new(false, &options.flag_bin, &[],
+ &options.flag_example, &[]),
target_rustc_args: None,
};
- let root = &Path::new("$HOME/.cargo/bin");
+ let source = if let Some(url) = options.flag_git {
+ let url = try!(url.to_url().map_err(human));
+ let gitref = if let Some(branch) = options.flag_branch {
+ GitReference::Branch(branch)
+ } else if let Some(tag) = options.flag_tag {
+ GitReference::Tag(tag)
+ } else if let Some(rev) = options.flag_rev {
+ GitReference::Rev(rev)
+ } else {
+ GitReference::Branch("master".to_string())
+ };
+ SourceId::for_git(&url, gitref)
+ } else if let Some(path) = options.flag_path {
+ try!(SourceId::for_path(Path::new(&path)))
+ } else {
+ try!(SourceId::for_central(config))
+ };
+
+ let krate = options.arg_crate.as_ref().map(|s| &s[..]);
+ let vers = options.flag_vers.as_ref().map(|s| &s[..]);
+ let root = options.flag_root.as_ref().map(|s| &s[..]);
- ops::install(&root,
- &compile_opts).map_err(|err| {
- CliError::from_boxed(err, 101)
- }).map(|_| None)
+ if options.flag_list {
+ try!(ops::install_list(root, config));
+ } else {
+ try!(ops::install(root, krate, &source, vers, &compile_opts));
+ }
+ Ok(None)
}
--- /dev/null
+use cargo::ops;
+use cargo::util::{CliResult, Config};
+
+#[derive(RustcDecodable)]
+struct Options {
+ flag_bin: Vec<String>,
+ flag_root: Option<String>,
+ flag_verbose: bool,
+ flag_quiet: bool,
+ flag_color: Option<String>,
+
+ arg_spec: String,
+}
+
+pub const USAGE: &'static str = "
+Remove a Rust binary
+
+Usage:
+ cargo uninstall [options] <spec>
+
+Options:
+ -h, --help Print this message
+ --root DIR Directory to uninstall packages from
+ --bin NAME Only uninstall the binary NAME
+ -v, --verbose Use verbose output
+ -q, --quiet Less output printed to stdout
+ --color WHEN Coloring: auto, always, never
+
+The argument SPEC is a package id specification (see `cargo help pkgid`) to
+specify which crate should be uninstalled. By default all binaries are
+uninstalled for a crate but the `--bin` and `--example` flags can be used to
+only uninstall particular binaries.
+";
+
+pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
+ try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet));
+ try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..])));
+
+ let root = options.flag_root.as_ref().map(|s| &s[..]);
+ try!(ops::uninstall(root, &options.arg_spec, &options.flag_bin, config));
+ Ok(None)
+}
+
+use std::collections::HashMap;
use std::fmt;
+
use semver::Version;
use url::{self, Url, UrlParser};
})
}
+ pub fn query_str<'a, I>(spec: &str, i: I) -> CargoResult<&'a PackageId>
+ where I: IntoIterator<Item=&'a PackageId>
+ {
+ let spec = try!(PackageIdSpec::parse(spec).chain_error(|| {
+ human(format!("invalid package id specification: `{}`", spec))
+ }));
+ spec.query(i)
+ }
+
pub fn from_package_id(package_id: &PackageId) -> PackageIdSpec {
PackageIdSpec {
name: package_id.name().to_string(),
None => true
}
}
+
+ pub fn query<'a, I>(&self, i: I) -> CargoResult<&'a PackageId>
+ where I: IntoIterator<Item=&'a PackageId>
+ {
+ let mut ids = i.into_iter().filter(|p| self.matches(*p));
+ let ret = match ids.next() {
+ Some(id) => id,
+ None => return Err(human(format!("package id specification `{}` \
+ matched no packages", self))),
+ };
+ return match ids.next() {
+ Some(other) => {
+ let mut msg = format!("There are multiple `{}` packages in \
+ your project, and the specification \
+ `{}` is ambiguous.\n\
+ Please re-run this command \
+ with `-p <spec>` where `<spec>` is one \
+ of the following:",
+ self.name(), self);
+ let mut vec = vec![ret, other];
+ vec.extend(ids);
+ minimize(&mut msg, vec, self);
+ Err(human(msg))
+ }
+ None => Ok(ret)
+ };
+
+ fn minimize(msg: &mut String,
+ ids: Vec<&PackageId>,
+ spec: &PackageIdSpec) {
+ let mut version_cnt = HashMap::new();
+ for id in ids.iter() {
+ *version_cnt.entry(id.version()).or_insert(0) += 1;
+ }
+ for id in ids.iter() {
+ if version_cnt[id.version()] == 1 {
+ msg.push_str(&format!("\n {}:{}", spec.name(),
+ id.version()));
+ } else {
+ msg.push_str(&format!("\n {}",
+ PackageIdSpec::from_package_id(*id)));
+ }
+ }
+ }
+ }
}
fn url(s: &str) -> url::ParseResult<Url> {
Ok(())
}
+ pub fn add_preloaded(&mut self, id: &SourceId, source: Box<Source + 'cfg>) {
+ self.add_source(id, source, Kind::Locked);
+ }
+
+ fn add_source(&mut self, id: &SourceId, source: Box<Source + 'cfg>,
+ kind: Kind) {
+ self.sources.insert(id, source);
+ self.source_ids.insert(id.clone(), (id.clone(), kind));
+ }
+
pub fn add_overrides(&mut self, ids: Vec<SourceId>) -> CargoResult<()> {
for id in ids.iter() {
try!(self.load(id, Kind::Override));
}
// Save off the source
- self.sources.insert(source_id, source);
- self.source_ids.insert(source_id.clone(), (source_id.clone(), kind));
+ self.add_source(source_id, source, kind);
Ok(())
}).chain_error(|| human(format!("Unable to update {}", source_id)))
use core::{PackageId, Registry, SourceId, Summary, Dependency};
use core::PackageIdSpec;
-use util::{CargoResult, Graph, human, ChainError, CargoError};
+use util::{CargoResult, Graph, human, CargoError};
use util::profile;
use util::graph::{Nodes, Edges};
self.graph.edges(pkg)
}
- pub fn query(&self, spec: &str) -> CargoResult<&PackageId> {
- let spec = try!(PackageIdSpec::parse(spec).chain_error(|| {
- human(format!("invalid package id specification: `{}`", spec))
- }));
- let mut ids = self.iter().filter(|p| spec.matches(*p));
- let ret = match ids.next() {
- Some(id) => id,
- None => return Err(human(format!("package id specification `{}` \
- matched no packages", spec))),
- };
- return match ids.next() {
- Some(other) => {
- let mut msg = format!("There are multiple `{}` packages in \
- your project, and the specification \
- `{}` is ambiguous.\n\
- Please re-run this command \
- with `-p <spec>` where `<spec>` is one \
- of the following:",
- spec.name(), spec);
- let mut vec = vec![ret, other];
- vec.extend(ids);
- minimize(&mut msg, vec, &spec);
- Err(human(msg))
- }
- None => Ok(ret)
- };
-
- fn minimize(msg: &mut String,
- ids: Vec<&PackageId>,
- spec: &PackageIdSpec) {
- let mut version_cnt = HashMap::new();
- for id in ids.iter() {
- *version_cnt.entry(id.version()).or_insert(0) += 1;
- }
- for id in ids.iter() {
- if version_cnt[id.version()] == 1 {
- msg.push_str(&format!("\n {}:{}", spec.name(),
- id.version()));
- } else {
- msg.push_str(&format!("\n {}",
- PackageIdSpec::from_package_id(*id)));
- }
- }
- }
- }
-
pub fn features(&self, pkg: &PackageId) -> Option<&HashSet<String>> {
self.features.get(pkg)
}
+
+ pub fn query(&self, spec: &str) -> CargoResult<&PackageId> {
+ PackageIdSpec::query_str(spec, self.iter())
+ }
}
impl fmt::Debug for Resolve {
SourceId::new(Kind::Registry, url)
.with_precise(Some("locked".to_string()))
}
- "path" => SourceId::for_path(Path::new(&url[5..])).unwrap(),
+ "path" => {
+ let url = url.to_url().unwrap();
+ SourceId::new(Kind::Path, url)
+ }
_ => panic!("Unsupported serialized SourceId")
}
}
pub fn to_url(&self) -> String {
match *self.inner {
- SourceIdInner { kind: Kind::Path, .. } => {
- panic!("Path sources are not included in the lockfile, \
- so this is unimplemented")
- },
+ SourceIdInner { kind: Kind::Path, ref url, .. } => {
+ format!("path+{}", url)
+ }
SourceIdInner {
kind: Kind::Git(ref reference), ref url, ref precise, ..
} => {
for key in package.manifest().warnings().iter() {
try!(options.config.shell().warn(key))
}
- compile_pkg(&package, options)
+ compile_pkg(&package, None, options)
}
#[allow(deprecated)] // connect => join in 1.3
pub fn compile_pkg<'a>(root_package: &Package,
+ source: Option<Box<Source + 'a>>,
options: &CompileOptions<'a>)
-> CargoResult<ops::Compilation<'a>> {
let CompileOptions { config, jobs, target, spec, features,
let (packages, resolve_with_overrides, sources) = {
let mut registry = PackageRegistry::new(options.config);
+ if let Some(source) = source {
+ registry.add_preloaded(root_package.package_id().source_id(), source);
+ }
+
// First, resolve the root_package's *listed* dependencies, as well as
// downloading and updating all remotes and such.
let resolve = try!(ops::resolve_pkg(&mut registry, root_package));
-use ops;
-use util::CargoResult;
-use sources::PathSource;
-use std::path::Path;
+use std::collections::btree_map::Entry;
+use std::collections::{BTreeMap, BTreeSet};
+use std::env;
+use std::ffi::OsString;
+use std::fs::{self, File};
+use std::io::prelude::*;
+use std::path::{Path, PathBuf};
-pub fn install(manifest_path: &Path,
+use toml;
+
+use core::{SourceId, Source, Package, Registry, Dependency, PackageIdSpec};
+use core::PackageId;
+use ops::{self, CompileFilter};
+use sources::{GitSource, PathSource, RegistrySource};
+use util::{CargoResult, ChainError, Config, human, internal};
+
+#[derive(RustcDecodable, RustcEncodable)]
+enum CrateListing {
+ V1(CrateListingV1),
+}
+
+#[derive(RustcDecodable, RustcEncodable)]
+struct CrateListingV1 {
+ v1: BTreeMap<PackageId, BTreeSet<String>>,
+}
+
+struct Transaction {
+ bins: Vec<PathBuf>,
+}
+
+impl Drop for Transaction {
+ fn drop(&mut self) {
+ for bin in self.bins.iter() {
+ let _ = fs::remove_file(bin);
+ }
+ }
+}
+
+pub fn install(root: Option<&str>,
+ krate: Option<&str>,
+ source_id: &SourceId,
+ vers: Option<&str>,
opts: &ops::CompileOptions) -> CargoResult<()> {
let config = opts.config;
- let src = try!(PathSource::for_path(manifest_path.parent().unwrap(),
- config));
- let _root = try!(src.root_package());
+ let root = try!(resolve_root(root, config));
+ let (pkg, source) = if source_id.is_git() {
+ try!(select_pkg(GitSource::new(source_id, config), source_id,
+ krate, vers, &mut |git| git.read_packages()))
+ } else if source_id.is_path() {
+ let path = source_id.url().to_file_path().ok()
+ .expect("path sources must have a valid path");
+ try!(select_pkg(PathSource::new(&path, source_id, config),
+ source_id, krate, vers,
+ &mut |path| path.read_packages()))
+ } else {
+ try!(select_pkg(RegistrySource::new(source_id, config),
+ source_id, krate, vers,
+ &mut |_| Err(human("must specify a crate to install from \
+ crates.io"))))
+ };
+
+ let mut list = try!(read_crate_list(&root));
+ let dst = root.join("bin");
+ try!(check_overwrites(&dst, &pkg, &opts.filter, &list));
+
+ let target_dir = config.cwd().join("target-install");
+ config.set_target_dir(&target_dir);
+ let compile = try!(ops::compile_pkg(&pkg, Some(source), opts).chain_error(|| {
+ human(format!("failed to compile `{}`, intermediate artifacts can be \
+ found at `{}`", pkg, target_dir.display()))
+ }));
+
+ let mut t = Transaction { bins: Vec::new() };
+ try!(fs::create_dir_all(&dst));
+ for bin in compile.binaries.iter() {
+ let dst = dst.join(bin.file_name().unwrap());
+ try!(config.shell().status("Installing", dst.display()));
+ try!(fs::copy(&bin, &dst).chain_error(|| {
+ human(format!("failed to copy `{}` to `{}`", bin.display(),
+ dst.display()))
+ }));
+ t.bins.push(dst);
+ }
+ try!(fs::remove_dir_all(&target_dir));
+
+ list.v1.entry(pkg.package_id().clone()).or_insert_with(|| {
+ BTreeSet::new()
+ }).extend(t.bins.iter().map(|t| {
+ t.file_name().unwrap().to_string_lossy().into_owned()
+ }));
+ try!(write_crate_list(&root, list));
+
+ t.bins.truncate(0);
+
+ // Print a warning that if this directory isn't in PATH that they won't be
+ // able to run these commands.
+ let path = env::var_os("PATH").unwrap_or(OsString::new());
+ for path in env::split_paths(&path) {
+ if path == dst {
+ return Ok(())
+ }
+ }
+
+ try!(config.shell().warn(&format!("be sure to add `{}` to your PATH to be \
+ able to run the installed binaries",
+ dst.display())));
+ Ok(())
+}
+
+fn select_pkg<'a, T>(mut source: T,
+ source_id: &SourceId,
+ name: Option<&str>,
+ vers: Option<&str>,
+ list_all: &mut FnMut(&mut T) -> CargoResult<Vec<Package>>)
+ -> CargoResult<(Package, Box<Source + 'a>)>
+ where T: Source + 'a
+{
+ try!(source.update());
+ match name {
+ Some(name) => {
+ let dep = try!(Dependency::parse(name, vers, source_id));
+ let deps = try!(source.query(&dep));
+ match deps.iter().map(|p| p.package_id()).max() {
+ Some(pkgid) => {
+ try!(source.download(&[pkgid.clone()]));
+ Ok((try!(source.get(&[pkgid.clone()])).remove(0),
+ Box::new(source)))
+ }
+ None => {
+ let vers_info = vers.map(|v| format!(" with version `{}`", v))
+ .unwrap_or(String::new());
+ Err(human(format!("could not find `{}` in `{}`{}", name,
+ source_id, vers_info)))
+ }
+ }
+ }
+ None => {
+ let candidates = try!(list_all(&mut source));
+ let binaries = candidates.iter().filter(|cand| {
+ cand.targets().iter().filter(|t| t.is_bin()).count() > 0
+ });
+ let examples = candidates.iter().filter(|cand| {
+ cand.targets().iter().filter(|t| t.is_example()).count() > 0
+ });
+ let pkg = match try!(one(binaries, |v| multi_err("binaries", v))) {
+ Some(p) => p,
+ None => {
+ match try!(one(examples, |v| multi_err("examples", v))) {
+ Some(p) => p,
+ None => return Err(human("no packages found with \
+ binaries or examples")),
+ }
+ }
+ };
+ return Ok((pkg.clone(), Box::new(source)));
- println!("Compiling");
- try!(ops::compile(manifest_path, opts));
+ #[allow(deprecated)] // connect => join in 1.3
+ fn multi_err(kind: &str, mut pkgs: Vec<&Package>) -> String {
+ pkgs.sort_by(|a, b| a.name().cmp(b.name()));
+ format!("multiple packages with {} found: {}", kind,
+ pkgs.iter().map(|p| p.name()).collect::<Vec<_>>()
+ .connect(", "))
+ }
+ }
+ }
+}
+
+fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
+ where I: Iterator,
+ F: FnOnce(Vec<I::Item>) -> String
+{
+ match (i.next(), i.next()) {
+ (Some(i1), Some(i2)) => {
+ let mut v = vec![i1, i2];
+ v.extend(i);
+ Err(human(f(v)))
+ }
+ (Some(i), None) => Ok(Some(i)),
+ (None, _) => Ok(None)
+ }
+}
+
+fn check_overwrites(dst: &Path,
+ pkg: &Package,
+ filter: &ops::CompileFilter,
+ prev: &CrateListingV1) -> CargoResult<()> {
+ let check = |name| {
+ let name = format!("{}{}", name, env::consts::EXE_SUFFIX);
+ if fs::metadata(dst.join(&name)).is_err() {
+ return Ok(())
+ }
+ let mut msg = format!("binary `{}` already exists in destination", name);
+ if let Some((p, _)) = prev.v1.iter().find(|&(_, v)| v.contains(&name)) {
+ msg.push_str(&format!(" as part of `{}`", p));
+ }
+ Err(human(msg))
+ };
+ match *filter {
+ CompileFilter::Everything => {
+ // If explicit --bin or --example flags were passed then those'll
+ // get checked during cargo_compile, we only care about the "build
+ // everything" case here
+ if pkg.targets().iter().filter(|t| t.is_bin()).next().is_none() {
+ return Err(human("specified package has no binaries"))
+ }
+
+ for target in pkg.targets().iter().filter(|t| t.is_bin()) {
+ try!(check(target.name()));
+ }
+ }
+ CompileFilter::Only { bins, examples, .. } => {
+ for bin in bins.iter().chain(examples) {
+ try!(check(bin));
+ }
+ }
+ }
+ Ok(())
+}
+
+fn read_crate_list(path: &Path) -> CargoResult<CrateListingV1> {
+ let metadata = path.join(".crates.toml");
+ let mut f = match File::open(&metadata) {
+ Ok(f) => f,
+ Err(..) => return Ok(CrateListingV1 { v1: BTreeMap::new() }),
+ };
+ (|| -> CargoResult<_> {
+ let mut contents = String::new();
+ try!(f.read_to_string(&mut contents));
+ let listing = try!(toml::decode_str(&contents).chain_error(|| {
+ internal("invalid TOML found for metadata")
+ }));
+ match listing {
+ CrateListing::V1(v1) => Ok(v1),
+ }
+ }).chain_error(|| {
+ human(format!("failed to parse crate metadata at `{}`",
+ metadata.display()))
+ })
+}
+
+fn write_crate_list(path: &Path, listing: CrateListingV1) -> CargoResult<()> {
+ let metadata = path.join(".crates.toml");
+ (|| -> CargoResult<_> {
+ let mut f = try!(File::create(&metadata));
+ let data = toml::encode_str::<CrateListing>(&CrateListing::V1(listing));
+ try!(f.write_all(data.as_bytes()));
+ Ok(())
+ }).chain_error(|| {
+ human(format!("failed to write crate metadata at `{}`",
+ metadata.display()))
+ })
+}
+
+pub fn install_list(dst: Option<&str>, config: &Config) -> CargoResult<()> {
+ let dst = try!(resolve_root(dst, config));
+ let list = try!(read_crate_list(&dst));
+ let mut shell = config.shell();
+ let out = shell.out();
+ for (k, v) in list.v1.iter() {
+ try!(writeln!(out, "{}:", k));
+ for bin in v {
+ try!(writeln!(out, " {}", bin));
+ }
+ }
+ Ok(())
+}
+
+pub fn uninstall(root: Option<&str>,
+ spec: &str,
+ bins: &[String],
+ config: &Config) -> CargoResult<()> {
+ let root = try!(resolve_root(root, config));
+ let mut metadata = try!(read_crate_list(&root));
+ let mut to_remove = Vec::new();
+ {
+ let result = try!(PackageIdSpec::query_str(spec, metadata.v1.keys()))
+ .clone();
+ let mut installed = match metadata.v1.entry(result.clone()) {
+ Entry::Occupied(e) => e,
+ Entry::Vacant(..) => panic!("entry not found: {}", result),
+ };
+ let dst = root.join("bin");
+ for bin in installed.get() {
+ let bin = dst.join(bin);
+ if fs::metadata(&bin).is_err() {
+ return Err(human(format!("corrupt metadata, `{}` does not \
+ exist when it should",
+ bin.display())))
+ }
+ }
+
+ let bins = bins.iter().map(|s| {
+ if s.ends_with(env::consts::EXE_SUFFIX) {
+ s.to_string()
+ } else {
+ format!("{}{}", s, env::consts::EXE_SUFFIX)
+ }
+ }).collect::<Vec<_>>();
+
+ for bin in bins.iter() {
+ if !installed.get().contains(bin) {
+ return Err(human(format!("binary `{}` not installed as part \
+ of `{}`", bin, result)))
+ }
+ }
+
+ if bins.len() == 0 {
+ to_remove.extend(installed.get().iter().map(|b| dst.join(b)));
+ installed.get_mut().clear();
+ } else {
+ for bin in bins.iter() {
+ to_remove.push(dst.join(bin));
+ installed.get_mut().remove(bin);
+ }
+ }
+ if installed.get().len() == 0 {
+ installed.remove();
+ }
+ }
+ try!(write_crate_list(&root, metadata));
+ for bin in to_remove {
+ try!(config.shell().status("Removing", bin.display()));
+ try!(fs::remove_file(bin));
+ }
Ok(())
}
+
+fn resolve_root(flag: Option<&str>, config: &Config) -> CargoResult<PathBuf> {
+ let config_root = try!(config.get_string("install.root"));
+ Ok(flag.map(PathBuf::from).or_else(|| {
+ env::var_os("CARGO_INSTALL_ROOT").map(PathBuf::from)
+ }).or_else(|| {
+ config_root.clone().map(|(v, _)| PathBuf::from(v))
+ }).unwrap_or_else(|| {
+ config.home().to_owned()
+ }))
+}
let new_pkg = Package::new(new_manifest, &manifest_path);
// Now that we've rewritten all our path dependencies, compile it!
- try!(ops::compile_pkg(&new_pkg, &ops::CompileOptions {
+ try!(ops::compile_pkg(&new_pkg, None, &ops::CompileOptions {
config: config,
jobs: None,
target: None,
};
let pkgid = match spec {
- Some(spec) => try!(resolve.query(spec)),
+ Some(spec) => try!(PackageIdSpec::query_str(spec, resolve.iter())),
None => package.package_id(),
};
Ok(PackageIdSpec::from_package_id(pkgid))
pub use self::cargo_rustc::{BuildOutput, BuildConfig, TargetConfig};
pub use self::cargo_rustc::{CommandType, CommandPrototype, ExecEngine, ProcessEngine};
pub use self::cargo_run::run;
-pub use self::cargo_install::install;
+pub use self::cargo_install::{install, install_list, uninstall};
pub use self::cargo_new::{new, NewOptions, VersionControl};
pub use self::cargo_doc::{doc, DocOptions};
pub use self::cargo_generate_lockfile::{generate_lockfile};
let resolve = try!(resolve_with_previous(registry, package,
Method::Everything,
prev.as_ref(), None));
- try!(ops::write_pkg_lockfile(package, &resolve));
+ if package.package_id().source_id().is_path() {
+ try!(ops::write_pkg_lockfile(package, &resolve));
+ }
Ok(resolve)
}
}
pub fn url(&self) -> &Url { self.remote.url() }
+
+ pub fn read_packages(&mut self) -> CargoResult<Vec<Package>> {
+ if self.path_source.is_none() {
+ try!(self.update());
+ }
+ self.path_source.as_mut().unwrap().read_packages()
+ }
}
fn ident(url: &Url) -> String {
}
}
- fn read_packages(&self) -> CargoResult<Vec<Package>> {
+ pub fn read_packages(&self) -> CargoResult<Vec<Package>> {
if self.updated {
Ok(self.packages.clone())
} else if self.id.is_path() && self.id.precise().is_some() {
src_path: PathBuf,
config: &'cfg Config,
handle: Option<http::Handle>,
- sources: Vec<PathSource<'cfg>>,
+ sources: HashMap<PackageId, PathSource<'cfg>>,
hashes: HashMap<(String, String), String>, // (name, vers) => cksum
cache: HashMap<String, Vec<(Summary, bool)>>,
updated: bool,
config: config,
source_id: source_id.clone(),
handle: None,
- sources: Vec::new(),
+ sources: HashMap::new(),
hashes: HashMap::new(),
cache: HashMap::new(),
updated: false,
}
/// Parse the on-disk metadata for the package provided
- fn summaries(&mut self, name: &str) -> CargoResult<&Vec<(Summary, bool)>> {
+ pub fn summaries(&mut self, name: &str) -> CargoResult<&Vec<(Summary, bool)>> {
if self.cache.contains_key(name) {
return Ok(self.cache.get(name).unwrap());
}
let url = try!(config.dl.to_url().map_err(internal));
for package in packages.iter() {
if self.source_id != *package.source_id() { continue }
+ if self.sources.contains_key(package) { continue }
let mut url = url.clone();
url.path_mut().unwrap().push(package.name().to_string());
}));
let mut src = PathSource::new(&path, &self.source_id, self.config);
try!(src.update());
- self.sources.push(src);
+ self.sources.insert(package.clone(), src);
}
Ok(())
}
fn get(&self, packages: &[PackageId]) -> CargoResult<Vec<Package>> {
let mut ret = Vec::new();
- for src in self.sources.iter() {
+ for src in self.sources.values() {
ret.extend(try!(src.get(packages)).into_iter());
}
return Ok(ret);
cwd: PathBuf,
rustc: PathBuf,
rustdoc: PathBuf,
- target_dir: Option<PathBuf>,
+ target_dir: RefCell<Option<PathBuf>>,
}
impl Config {
values_loaded: Cell::new(false),
rustc: PathBuf::from("rustc"),
rustdoc: PathBuf::from("rustdoc"),
- target_dir: None,
+ target_dir: RefCell::new(None),
};
try!(cfg.scrape_tool_config());
pub fn cwd(&self) -> &Path { &self.cwd }
pub fn target_dir(&self, pkg: &Package) -> PathBuf {
- self.target_dir.clone().unwrap_or_else(|| {
+ self.target_dir.borrow().clone().unwrap_or_else(|| {
pkg.root().join("target")
})
}
+ pub fn set_target_dir(&self, path: &Path) {
+ *self.target_dir.borrow_mut() = Some(path.to_owned());
+ }
+
pub fn get(&self, key: &str) -> CargoResult<Option<ConfigValue>> {
let vals = try!(self.values());
let mut parts = key.split('.').enumerate();
path.pop();
path.pop();
path.push(dir);
- self.target_dir = Some(path);
+ *self.target_dir.borrow_mut() = Some(path);
} else if let Some(dir) = env::var_os("CARGO_TARGET_DIR") {
- self.target_dir = Some(self.cwd.join(dir));
+ *self.target_dir.borrow_mut() = Some(self.cwd.join(dir));
}
Ok(())
}
pub static UPLOADING: &'static str = " Uploading";
pub static VERIFYING: &'static str = " Verifying";
pub static ARCHIVING: &'static str = " Archiving";
-pub static INSTALLED: &'static str = " Installed";
+pub static INSTALLING: &'static str = " Installing";
}
let p = project(name)
.file("Cargo.toml", &manifest)
- .file("src/lib.rs", "");
+ .file("src/lib.rs", "")
+ .file("src/main.rs", &format!("\
+ extern crate {};
+ fn main() {{}}
+ ", name));
p.build();
let dst = mock_archive_dst(name, version);
&mut File::open(&p.root().join("Cargo.toml")).unwrap()).unwrap();
a.append_file(&format!("{}-{}/src/lib.rs", name, version),
&mut File::open(&p.root().join("src/lib.rs")).unwrap()).unwrap();
+ a.append_file(&format!("{}-{}/src/main.rs", name, version),
+ &mut File::open(&p.root().join("src/main.rs")).unwrap()).unwrap();
a.finish().unwrap();
}
--- /dev/null
+use std::fmt;
+use std::fs::{self, File};
+use std::io::prelude::*;
+use std::path::{Path, PathBuf};
+
+use cargo::util::{process, ProcessBuilder};
+use hamcrest::{assert_that, existing_file, is_not, Matcher, MatchResult};
+
+use support::{project, execs, cargo_dir};
+use support::{UPDATING, DOWNLOADING, COMPILING, INSTALLING, REMOVING};
+use support::paths;
+use support::registry as r;
+use support::git;
+
+use self::InstalledExe as has_installed_exe;
+
+fn setup() {
+ r::init();
+}
+
+fn cargo_process(s: &str) -> ProcessBuilder {
+ let mut p = process(&cargo_dir().join("cargo")).unwrap();
+ p.arg(s).cwd(&paths::root())
+ .env("HOME", &paths::home())
+ .env_remove("CARGO_HOME");
+ return p;
+}
+
+fn exe(name: &str) -> String {
+ if cfg!(windows) {format!("{}.exe", name)} else {name.to_string()}
+}
+
+fn cargo_home() -> PathBuf {
+ paths::home().join(".cargo")
+}
+
+struct InstalledExe(&'static str);
+
+impl<P: AsRef<Path>> Matcher<P> for InstalledExe {
+ fn matches(&self, path: P) -> MatchResult {
+ let path = path.as_ref().join("bin").join(exe(self.0));
+ existing_file().matches(&path)
+ }
+}
+
+impl fmt::Display for InstalledExe {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "installed exe `{}`", self.0)
+ }
+}
+
+test!(simple {
+ r::mock_pkg("foo", "0.0.1", &[]);
+
+ assert_that(cargo_process("install").arg("foo"),
+ execs().with_status(0).with_stdout(&format!("\
+{updating} registry `[..]`
+{downloading} foo v0.0.1 (registry file://[..])
+{compiling} foo v0.0.1 (registry file://[..])
+{installing} {home}[..]bin[..]foo[..]
+",
+ updating = UPDATING,
+ downloading = DOWNLOADING,
+ compiling = COMPILING,
+ installing = INSTALLING,
+ home = cargo_home().display())));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+
+ assert_that(cargo_process("uninstall").arg("foo"),
+ execs().with_status(0).with_stdout(&format!("\
+{removing} {home}[..]bin[..]foo[..]
+",
+ removing = REMOVING,
+ home = cargo_home().display())));
+ assert_that(cargo_home(), is_not(has_installed_exe("foo")));
+});
+
+test!(pick_max_version {
+ r::mock_pkg("foo", "0.0.1", &[]);
+ r::mock_pkg("foo", "0.0.2", &[]);
+
+ assert_that(cargo_process("install").arg("foo"),
+ execs().with_status(0).with_stdout(&format!("\
+{updating} registry `[..]`
+{downloading} foo v0.0.2 (registry file://[..])
+{compiling} foo v0.0.2 (registry file://[..])
+{installing} {home}[..]bin[..]foo[..]
+",
+ updating = UPDATING,
+ downloading = DOWNLOADING,
+ compiling = COMPILING,
+ installing = INSTALLING,
+ home = cargo_home().display())));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+});
+
+test!(missing {
+ r::mock_pkg("foo", "0.0.1", &[]);
+ assert_that(cargo_process("install").arg("bar"),
+ execs().with_status(101).with_stderr("\
+could not find `bar` in `registry file://[..]`
+"));
+});
+
+test!(bad_version {
+ r::mock_pkg("foo", "0.0.1", &[]);
+ assert_that(cargo_process("install").arg("foo").arg("--vers=0.2.0"),
+ execs().with_status(101).with_stderr("\
+could not find `foo` in `registry file://[..]` with version `0.2.0`
+"));
+});
+
+test!(no_crate {
+ assert_that(cargo_process("install"),
+ execs().with_status(101).with_stderr("\
+must specify a crate to install from crates.io
+"));
+});
+
+test!(install_location_precedence {
+ r::mock_pkg("foo", "0.0.1", &[]);
+
+ let root = paths::root();
+ let t1 = root.join("t1");
+ let t2 = root.join("t2");
+ let t3 = root.join("t3");
+ let t4 = cargo_home();
+
+ fs::create_dir(root.join(".cargo")).unwrap();
+ File::create(root.join(".cargo/config")).unwrap().write_all(format!("\
+ [install]
+ root = '{}'
+ ", t3.display()).as_bytes()).unwrap();
+
+ println!("install --root");
+
+ assert_that(cargo_process("install").arg("foo")
+ .arg("--root").arg(&t1)
+ .env("CARGO_INSTALL_ROOT", &t2),
+ execs().with_status(0));
+ assert_that(&t1, has_installed_exe("foo"));
+ assert_that(&t2, is_not(has_installed_exe("foo")));
+
+ println!("install CARGO_INSTALL_ROOT");
+
+ assert_that(cargo_process("install").arg("foo")
+ .env("CARGO_INSTALL_ROOT", &t2),
+ execs().with_status(0));
+ assert_that(&t2, has_installed_exe("foo"));
+ assert_that(&t3, is_not(has_installed_exe("foo")));
+
+ println!("install install.root");
+
+ assert_that(cargo_process("install").arg("foo"),
+ execs().with_status(0));
+ assert_that(&t3, has_installed_exe("foo"));
+ assert_that(&t4, is_not(has_installed_exe("foo")));
+
+ fs::remove_file(root.join(".cargo/config")).unwrap();
+
+ println!("install cargo home");
+
+ assert_that(cargo_process("install").arg("foo"),
+ execs().with_status(0));
+ assert_that(&t4, has_installed_exe("foo"));
+});
+
+test!(install_path {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/main.rs", "fn main() {}");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()),
+ execs().with_status(0));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+});
+
+test!(multiple_crates_error {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/main.rs", "fn main() {}")
+ .file("a/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("a/src/main.rs", "fn main() {}");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()),
+ execs().with_status(101).with_stderr("\
+multiple packages with binaries found: bar, foo
+"));
+});
+
+test!(multiple_crates_select {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/main.rs", "fn main() {}")
+ .file("a/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("a/src/main.rs", "fn main() {}");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()).arg("foo"),
+ execs().with_status(0));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+ assert_that(cargo_home(), is_not(has_installed_exe("bar")));
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()).arg("bar"),
+ execs().with_status(0));
+ assert_that(cargo_home(), has_installed_exe("bar"));
+});
+
+test!(multiple_crates_auto_binaries {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "a" }
+ "#)
+ .file("src/main.rs", "extern crate bar; fn main() {}")
+ .file("a/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("a/src/lib.rs", "");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()),
+ execs().with_status(0));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+});
+
+test!(multiple_crates_auto_examples {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "a" }
+ "#)
+ .file("src/lib.rs", "extern crate bar;")
+ .file("examples/foo.rs", "
+ extern crate bar;
+ extern crate foo;
+ fn main() {}
+ ")
+ .file("a/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("a/src/lib.rs", "");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root())
+ .arg("--example=foo"),
+ execs().with_status(0));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+});
+
+test!(no_binaries_or_examples {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "a" }
+ "#)
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("a/src/lib.rs", "");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()),
+ execs().with_status(101).with_stderr("\
+no packages found with binaries or examples
+"));
+});
+
+test!(no_binaries {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "")
+ .file("examples/foo.rs", "fn main() {}");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()).arg("foo"),
+ execs().with_status(101).with_stderr("\
+specified package has no binaries
+"));
+});
+
+test!(examples {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "")
+ .file("examples/foo.rs", "extern crate foo; fn main() {}");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root())
+ .arg("--example=foo"),
+ execs().with_status(0));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+});
+
+test!(install_twice {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/main.rs", "fn main() {}");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()),
+ execs().with_status(0));
+ assert_that(cargo_process("install").arg("--path").arg(p.root()),
+ execs().with_status(101).with_stderr("\
+binary `foo[..]` already exists in destination as part of `foo v0.1.0 ([..])`
+"));
+});
+
+test!(compile_failure {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/main.rs", "");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()),
+ execs().with_status(101).with_stderr("\
+error: main function not found
+error: aborting due to previous error
+failed to compile `foo v0.1.0 (file://[..])`, intermediate artifacts can be \
+ found at `[..]target-install`
+
+Caused by:
+ Could not compile `foo`.
+
+To learn more, run the command again with --verbose.
+"));
+});
+
+test!(git_repo {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/main.rs", "fn main() {}");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--git").arg(p.url().to_string()),
+ execs().with_status(0).with_stdout(&format!("\
+{updating} git repository `[..]`
+{compiling} foo v0.1.0 ([..])
+{installing} {home}[..]bin[..]foo[..]
+",
+ updating = UPDATING,
+ compiling = COMPILING,
+ installing = INSTALLING,
+ home = cargo_home().display())));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+});
+
+test!(list {
+ r::mock_pkg("foo", "0.0.1", &[]);
+ r::mock_pkg("bar", "0.2.1", &[]);
+ r::mock_pkg("bar", "0.2.2", &[]);
+
+ assert_that(cargo_process("install").arg("--list"),
+ execs().with_status(0).with_stdout(""));
+
+ assert_that(cargo_process("install").arg("bar").arg("--vers").arg("=0.2.1"),
+ execs().with_status(0));
+ assert_that(cargo_process("install").arg("foo"),
+ execs().with_status(0));
+ assert_that(cargo_process("install").arg("--list"),
+ execs().with_status(0).with_stdout("\
+bar v0.2.1 (registry [..]):
+ bar[..]
+foo v0.0.1 (registry [..]):
+ foo[..]
+"));
+});
+
+test!(uninstall_pkg_does_not_exist {
+ assert_that(cargo_process("uninstall").arg("foo"),
+ execs().with_status(101).with_stderr("\
+package id specification `foo` matched no packages
+"));
+});
+
+test!(uninstall_bin_does_not_exist {
+ r::mock_pkg("foo", "0.0.1", &[]);
+
+ assert_that(cargo_process("install").arg("foo"),
+ execs().with_status(0));
+ assert_that(cargo_process("uninstall").arg("foo").arg("--bin=bar"),
+ execs().with_status(101).with_stderr("\
+binary `bar[..]` not installed as part of `foo v0.0.1 ([..])`
+"));
+});
+
+test!(uninstall_piecemeal {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/bin/foo.rs", "fn main() {}")
+ .file("src/bin/bar.rs", "fn main() {}");
+ p.build();
+
+ assert_that(cargo_process("install").arg("--path").arg(p.root()),
+ execs().with_status(0));
+ assert_that(cargo_home(), has_installed_exe("foo"));
+ assert_that(cargo_home(), has_installed_exe("bar"));
+
+ assert_that(cargo_process("uninstall").arg("foo").arg("--bin=bar"),
+ execs().with_status(0).with_stdout(&format!("\
+{removing} [..]bar[..]
+", removing = REMOVING)));
+
+ assert_that(cargo_home(), has_installed_exe("foo"));
+ assert_that(cargo_home(), is_not(has_installed_exe("bar")));
+
+ assert_that(cargo_process("uninstall").arg("foo").arg("--bin=foo"),
+ execs().with_status(0).with_stdout(&format!("\
+{removing} [..]foo[..]
+", removing = REMOVING)));
+ assert_that(cargo_home(), is_not(has_installed_exe("foo")));
+
+ assert_that(cargo_process("uninstall").arg("foo"),
+ execs().with_status(101).with_stderr("\
+package id specification `foo` matched no packages
+"));
+});
mod test_cargo_fetch;
mod test_cargo_freshness;
mod test_cargo_generate_lockfile;
+mod test_cargo_install;
mod test_cargo_new;
mod test_cargo_package;
mod test_cargo_profiles;